C/C++开发,无可避免的字符串(篇二).STL字符串及字符处理函数

目录

一、字符指针及字符数组

        1.1 字符指针

        1.2 字符数组

        1.3 字符指针及字符数组关系

二、字符串处理

        2.1 标准库字符处理函数

        2.2 字符串操作

        2.3 字符串检验

        2.4 字符数组操作

        2.5 字符串处理类调用

三、字符处理

        3.1 字符操作函数集

        3.2 字符分类函数

        3.3 字符大小写转换函数

        3.4 字符串转数值格式

        3.5 字符处理业务调用

四、演示源代码补充


一、字符指针及字符数组

        1.1 字符指针

        一个char型数组在内存占据一个字节的存储空间,即8bits,而一个“char*”指针类型在内存上占据 4 个字节的空间,即32bits,然后把这个 4 个字节大小的空间命名为 p,指向一个存储char类型数据的连续空间的某个地址。“char*”指针类型限定在这 4 个字节的空间里面只能存储内存地址,即使存入其他任何数据,都会被当作地址处理,而且这个内存地址开始的连续存储空间上只能存储char类型的数据。

        其实指针类型都是在内存上占据 4 个字节的空间的,void*、int*、float*、UserDefClass*等,指针的初始值一般赋值为NULL(c++11前)或nullptr(c++11起)。相比起各个数据类型有不同的内存布局(哪怕是基本内置类型),指针类型的内存布局是一致的,因此,标准库在字符处理上,提供的大多函数都是针对字符指针的。一方面是因为大部分字符处理函数都是针对字符串的,而字符指针在标识字符串上有独特优势,尤其是在C语言上。另一方面,字符指针和其他类型指针一样能相互转化,尤其在很多涉及内存处理的字符串处理函数上,字符指针往往是通过转化为void*指针为形参传入内存处理函数的。例如:

//内存拷贝,定义于头文件 <cstring>
//从src所指向的对象复制count个字符到dest所指向的对象。两个对象都被转译成unsigned char的数组。
void* memcpy( void* dest, const void* src, std::size_t count );

        1.2 字符数组

        数组在内存布局上是一块连续的存储空间,该空间的数值间隔由数据类型决定,对于字符数组来说,就是每个字节即8bits存储一个字符。

        例如我们定义char cvec[10],这里定义了一个数组,其包含了10个char型的数据。我们可以用 cvec[0],cvec[1]等来访问数组里面的每一个元素。针对该定义,编译器根据指定的元素个数和元素的类型分配确定大小(元素类型大小1*元素个数10)的一块内存,并把这块内存的名字命名为 cvec。名字 cvec 一旦与这块内存匹配就不能被改变。 cvec[0],cvec[1]等为 cvec的元素,但并非元素的名字。数组的每一个元素都是没有名字的。

        字符数组需要明确以下几个概念:

  •  cvec[0]是数组cvec的首个字符元素,&cvec[0]是首元素的地址。
  •  cvec是数组名,只能作为右值,不能作为左值;
  •  cvec在代码中某些地方使用时,将转换为数组地址(代表的是数组首元素的首地址),例如函数形参、作为右值,而&cvec取得是数组cvec的首地址。
  • &cvec在数值上与&cvec[0]是一样的,但是意义不一样,一个是整个数组的首地址,一个是数组首个元素的地址。

        字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。然而,要注意这两种初始化形式并不完全相同,字符串字面值(第 2.2 节)包含一个额外的空字符(null)用于结束字符串。当使用字符串字面值来初始化创建的新数组时,将在新数组中加入空字符:

char ca1[] = {'C', '+', '+'};         // no null
char ca2[] = {'C', '+', '+', '\0'};   // explicit null
char ca3[] = "C++";                   // null terminator added automatically
char ca4[3]= "C++";                   //error,没有给null留出位置

        ca1 的维数是 3,而 ca2 和 ca3 的维数则是 4。使用一组字符字面值初始化字符数组时,一定要记得添加结束字符串的空字符。例如,下面的初始化将导致编译时的错误:

const char ch5[10] = "hello world";     // error: "hello world" is 12 elements

        上述字符串字面值包含了 10 个显式字符,存放该字符串的数组则必须有 12个元素--11个用于存储字符字面值,而 1 个用于存放空字符 null。 

        1.3 字符指针及字符数组关系

        字符指针和字符数组本质是两个不同的概念,但是由于它们在内存布局上极为相似有很容易纠缠在一起。

        字符数组在声明时就已经明确了内存空间大小,适合与存储固定大小的字符串,一般同时进行初始化。例如声明char cvec[10],存储空间大小为10字节,数组名 cvec 代表的是数组首元素的首地址而不是数组的首地址。&cvec 才是整个数组的首地址。cvec 本身的地址由编译器另外存储,一般开发者不需要知道。

        在元素访问上,字符数组可以通过下标直接访问(cvec[0]、cvec[1]、...),也可与通过数值地址+偏移量访问(*(cvec+0)、*(cvec+1)、...)。

    char cvec[10] = "char vec";
    // char cvec[10] = {'c','h','a','r',' ','v','e','c','\0','\0'};//本质上
    if(cvec[1]==*(cvec+1))//YES
        std::cout << "cvec[1] equal to *(cvec+1)\n";    //

        字符指针一般通过动态内存分配释放(new/delete、malloc/free、...),通常用来处理可变字符长度的字符串。声明char *pvec,可以直接赋值初始化,也可以先初始化为nullptr。任何存入指针变量 pvec 的数据都会被当作地址来处理。pvec本身的地址由编译器另外存储,存储在哪里一般开发者也不必关心。

    char *pvec = nullptr;
    pvec = new char[10];
    delete[] pvec;
    pvec = nullptr;

        字符指针访问元素时,首先取得指针变量 pvec 的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据,可以以指针的形式访问*(pvec+i)或以下标的形式访问 pvec[i]。

    char cvec[10] = "char vec";

    char *pvec = cvec;
    if(pvec[1]==*(pvec+1))//YES
        std::cout << "pvec[1] equal to *(pvec+1)\n";

        从字符数组和字符指针的用法可以看到,它们具有极高相似性,代码开发实践中,它们常常依据使用场景需要相互转换角色。

#include <iostream>
#include <cstring>
#include <cassert>

void f(char* pvec)
{
    assert(nullptr!=pvec);
    std::cout << std::string(pvec) << "\n";
}

void g(char cvev[])
{
    assert(nullptr!=cvev);
    std::cout << std::string(cvev) << "\n";
}
void char_test(void)
{
    char cc[] = "hello world!";
    f(cc);
    char* pc = new char[sizeof(char)*sizeof(cc)];
    memcpy(pc,cc,sizeof(char)*sizeof(cc));
    g(pc);
    delete[] pc;
    pc = nullptr;
}

二、字符串处理

        2.1 标准库字符处理函数

        字符串操作最常见的就是字符串拷贝、拼接、计算长度、比较等,这些功能函数一般在<cstring>头文件提供,大多字符串函数底层都会调用这些函数来实现,例如std::string(即std::basic_string<char>)。

定义于头文件 <cstring>
【1】字符串操作
strcpy       复制一个字符串给另一个(函数) 
strncpy      复制来自一个字符串的一定量字符给另一个(函数) 
strcat       连接两个字符串(函数) 
strncat      连接两个字符串的一定量字符(函数) 
strxfrm      变换字符串,使得 strcmp 会返回与 strcoll 相同的结果(函数) 

【2】字符串检验
strlen      返回给定字符串的长度(函数) 
strcmp      比较两个字符串(函数) 
strncmp     比较两个字符串的一定量字符(函数) 
strcoll     按照当前本地环境比较两个字符串(函数) 
strchr      寻找字符的首次出现(函数) 
strrchr     寻找字符的最后出现(函数) 
strspn      返回仅由另一字节字符串中找到的字符组成的最大起始段的长度(函数) 
strcspn     返回仅由另一字节字符串中找不到的字符组成的最大起始段的长度(函数) 
strpbrk     寻找任何来自分隔符集合的字符的首个位置(函数) 
strstr      寻找字符子串的首次出现(函数) 
strtok      寻找字节字符串中的下个记号(函数) 

【3】字符数组操作 
memchr      在数组中搜索字符的首次出现(函数) 
memcmp      比较两个缓冲区(函数) 
memset      以一个字符填充缓冲区(函数) 
memcpy      复制一个缓冲区到另一个(函数) 
memmove     移动一个缓冲区到另一个(函数) 
【4】杂项
strerror    返回给定错误码的文本版本(函数) 

        2.2 字符串操作

        在标准可头文件<cstring>中定义了字符串操作,如strcpy、strcat等:

//定义于头文件 <cstring>
/*
*复制 src 所指向的字符串,包含空终止符,到首元素为 dest 所指向的字符数组。
*若 dest 数组不够大则行为未定义。若字符串重叠则行为未定义。
*参数
*dest     - 指向要写入的字符数组的指针 
*src      - 指向复制来源的空终止字节字符串的指针 
*返回值   -dest
*/
char* strcpy( char* dest, const char* src ); 

/*
*后附 src 所指向的空终止字节字符串的副本到 dest 所指向的空终止字节字符串的结尾。
*字符 src[0] 替换 dest 末尾的空终止符。产生的字节字符串是空终止的。
*若目标数组对于 src 和 dest 的内容以及空终止符不够大,则行为未定义。
*若字符串重叠,则行为未定义。
*参数
*dest     - 指向要后附到的空终止字节字符串的指针 
*src      - 指向作为复制来源的空终止字节字符串的指针 
*返回值    - dest
*/
char *strcat( char *dest, const char *src );

        这些函数见名知意,很好理解,现看看它们的用法,将通过它们来定义一个类似于仿std::string的字符串类MyString:

//MyString.h
#include <ostream>
class MyString
{
public:
	MyString(void);							//默认构造函数
	MyString( const char *str = nullptr );	//普通构造函数
	MyString( const MyString &other );		//拷贝构造函数
	virtual ~MyString( void );				//析构函数
	MyString& operator=(const MyString &other);	//赋值函数
    MyString& operator=(const char* other);	    //赋值函数
    operator char*() const;                     //MyString转char*
	char* c_str(void) const;					//取值(取值)
    //
    MyString& operator+=(const MyString &other);//赋值运算函数
private:
    void init(const char *str);
    void copy(const char *str);
private:
	char *m_data;
};
//MyString.cpp
#include "MyString.h"

#include <cstring>
//默认构造函数
MyString::MyString(void)
{
	MyString(nullptr);	//内部调用普通构造函数
}
//普通构造函数
MyString::MyString(const char *str)
{
    init(str);
}
// MyString 的析构函数
MyString::~MyString(void)
{
	delete [] m_data; // 或 delete m_data;
}

void MyString::init(const char *str)
{
	if(nullptr==str)
	{
		m_data = new char[1];	//对空字符串自动申请存放结束标志'\0'的空
		*m_data = '\0';
	}else{
		int length = strlen(str);    //调用标准库的字符串长度计算函数
		m_data = new char[length+1]; // 分配内存
		strcpy(m_data, str);         //调用标准库的字符串拷贝函数
	}
};

void MyString::copy(const char *str)
{
    if(nullptr!=str)
    {
        delete [] m_data;				//释放原有的内存资源
        int length = strlen( str );     //调用标准库的字符串长度计算函数
        m_data = new char[length+1];	//重新分配内存
        strcpy( m_data, str);           //调用标准库的字符串拷贝函数
    }
};

//拷贝构造函数
MyString::MyString( const MyString &other ) //输入参数为const型
{
    init(other.m_data);
}

//赋值函数
MyString &MyString::operator =( const MyString &other )//输入参数为const型
{
	if(this == &other)				//检查自赋值
		return *this;	
    copy(other.m_data);	
	return *this;					//返回本对象的引用
}

MyString& MyString::operator=(const char* other)	    //赋值函数
{
    copy(other);	
    return *this;					//返回本对象的引用
};

MyString::operator char*() const
{
    return (char*)m_data;
};

char* MyString::c_str(void) const
{
	return (char*)m_data;
};

MyString& MyString::operator+=(const MyString &other)
{
	if(nullptr==m_data)
	{
		init(other.m_data);
	}else{
		std::strcat(m_data,other.m_data);//调用标准库的字符串连接函数
	}
	return *this;
}

        上述示例代码中,定义了一个类似std::string功能的字符串处理类MyString,通过字符串拷贝函数std::strcpy实现了MyString类的复制构造函数、拷贝构造函数,通过std::strcat函数,实现了MyString类的operator+=操作符函数。

        2.3 字符串检验

        在标准可头文件<cstring>中定义了字符串操作,如strlen字符长度计算、strcmp字符串比较、strchar字符串查找字符等字符串校验功能函数:

//定义于头文件 <cstring>
/*返回给定字节字符串的长度,即首元素为 str 所指向的字符数组直到而不包含首个空字符的字符数。
*若 str 所指向的字符数组中无空字符,则行为未定义。
*参数str    - 指向要检验的空终止字节字符串的指针 
*返回值     -空终止字符串 str 的长度。
*/
std::size_t strlen( const char* str );

/*以字典序比较二个空终止字节字符串。
*结果的符号是被比较的字符串中首对不同字符(都转译成 unsigned char )的值间的差值符号。
*若 lhs 或 rhs 不是指向空终止字节字符串的指针,则行为未定义。
*参数 lhs, rhs - 指向待比较的空终止字节字符串的指针 
*返回值
*若字典序中 lhs 先出现于 rhs 则为负值。
*若 lhs 与 rhs 比较相等则为零。
*若字典序中 lhs 后出现于 rhs 则为正值。
*/
int strcmp( const char *lhs, const char *rhs );

/*
*在 str 所指向的字节字符串中寻找字符 static_cast<char>(ch) 的首次出现。
*认为终止空字符是字符串的一部分,而且若搜索 '\0' 则能找到它。
*参数: str - 指向待分析的空终止字节字符串的指针 , ch - 要搜索的字符 
*返回值 -- 指向 str 找到的字符的指针,若未找到该字符则为空指针。
*/
const char* strchr( const char* str, int ch );
  
char* strchr(char* str, int ch );

        下来看这些字符串校验功能函数的用法,依然通过MyString类:

//MyString.h
class MyString
{
public:
	//other...
    //返回m_data的长度
    std::size_t length();
    //指向 m_data 找到的字符的指针,若未找到该字符则为空指针。
    char* findChar(char c);
    //比较函数
    friend bool operator==(const MyString &lhs,const MyString &rhs);
};
//MyString.cpp
MyString& MyString::operator+=(const MyString &other)
{
	if(nullptr==m_data)
	{
		init(other.m_data);
	}else{
		std::strcat(m_data,other.m_data);
	}
	return *this;
}

std::size_t MyString::length()
{
	return std::strlen(m_data);
}

char* MyString::findChar(char c)
{
	return std::strchr(m_data,c);
}

bool operator==(const MyString &lhs,const MyString &rhs)
{
	/*std::strcmp
	*若字典序中 lhs 先出现于 rhs 则为负值。
	*若 lhs 与 rhs 比较相等则为零。
	*若字典序中 lhs 后出现于 rhs 则为正值。
	*/
	int ret = std::strcmp(lhs.m_data,rhs.m_data);
	return 0 == ret;
}

        上述示例中,共同std::strlen来实现MyString类的字符长度计算,通过std::strcmp函数实现MyString类的operator==比较运算符,std::strchar函数实现MyString类对象中查找特定字符。

        2.4 字符数组操作

        字符数组操作虽然也定义于头文件 <cstring>,主要是针对字符串处理,但是其本质上是针对内存操作,因此这个功能函数的形参时void*为主,而非字符串的指针。

//定义于头文件 <cstring>
/*
*从src所指向的对象复制count个字符到dest所指向的对象。两个对象都被转译成unsigned char的数组。
*若对象重叠,则行为未定义。若dest或src为非法或空指针则行为未定义,纵使count为零。
*若对象潜在重叠或不可平凡复制 (TriviallyCopyable) ,则 memcpy 的行为未指定而且可能未定义。
*参数
*dest    - 指向要复制的对象的指针 
*src     - 指向复制来源对象的指针 
*count   - 复制的字节数 
*返回值 - dest
*/
void* memcpy( void* dest, const void* src, std::size_t count );

/*
*转换值 ch 为 unsigned char 并复制它到 dest 所指向对象的首 count 个字节。
*若该对象是潜在重叠的子对象或非 可平凡复制 (TriviallyCopyable) ,则行为未定义。
*若 count 大于 dest 所指向的对象大小,则行为未定义。
*参数
*dest   - 指向要填充的对象的指针 
*ch     - 填充字节 
*count  - 要填充的字节数 
*返回值  - dest
*/
void* memset( void* dest, int ch, std::size_t count );

/*
*转译lhs和rhs所指向的对象为unsigned char数组,并比较这些数组的首count个字符。按字典序比较。
*结果的符号是在被比较对象中相异的首对字节的值(都转译成 unsigned char )的差。
*参数
*lhs, rhs  - 指向要比较的内存缓冲区的指针 
*count     - 要检验的字节数 
*返回值
*若 lhs 中首个相异字节(转译为 unsigned char )小于 rhs 中的对应字节则为负值。
*若 lhs 和 rhs 的所有 count 个字节相等则为 ​0​ 。
*若 lhs 中首个相异字节大于 rhs 中的对应字节则为正值。
*/
int memcmp( const void* lhs, const void* rhs, std::size_t count );

/*
*转换ch为unsigned char并在ptr所指向的对象的起始count(每个都转译为unsigned char)个字符中定位该值的首次出现。
*此函数表现如同它按顺序读取字符,并立即于找到匹配的字符时停止:
*若 ptr 所指向的字符数组小于 count ,但在数组中找到匹配,则行为良好定义。 (C++17 起) 
*参数
*ptr     - 指向要检验的对象的指针 
*ch      - 要搜索的字符 
*count   - 要检验的最大字符数 
*返回值 -- 指向字符位置的指针,或若找不到这种字符则为空指针。
*/
const void* memchr( const void* ptr, int ch, std::size_t count );
   
void* memchr(void* ptr, int ch, std::size_t count );

/*
*从src所指向的对象复制count个字节到dest所指向的对象。两个对象都被转译成unsigned char的数组。
*如同复制字符到临时数组,再从该数组到 dest 一般发生复制。
*若 dest 或 src 为非法或空指针则行为未定义,即使 count 为零。
*若对象潜在重叠或非可平凡复制 (TriviallyCopyable) ,则 memmove 的行为未指定,且可能未定义。
*参数
*dest - 指向复制目的的内存位置的指针 
*src - 指向复制来源的内存位置的指针 
*count - 要复制的字节数 
*返回值 - dest
*/
void* memmove( void* dest, const void* src, std::size_t count );

        这些功能函数在从操作字符串时,通常会将字符进行转译(unsigned char)然后传入到函数中来处理。借用这些函数操作,同样也能实现前面MyString类的复制构造、赋值运算、运算操作符、长度计算、字符查找等功能。

void MyString::init(const char *str)
{
	if(nullptr==str)
	{
		m_data = new char[1];	//对空字符串自动申请存放结束标志'\0'的空
		*m_data = '\0';
	}else{
		int length = strlen(str);
		m_data = new char[length+1];   // 分配内存
		// strcpy(m_data, str);		   //调用标准库的字符串拷贝函数
		std::memcpy(m_data,str,length);//调用标准库的字符数组拷贝函数
	}
};

void MyString::copy(const char *str)
{
    if(nullptr!=str)
    {
        delete [] m_data;				//释放原有的内存资源
        int length = strlen( str );
        m_data = new char[length+1];	//重新分配内存
        // strcpy( m_data, str);			//调用标准库的字符串拷贝函数
		std::memcpy(m_data,str,length); //调用标准库的字符数组拷贝函数
    }
};

MyString& MyString::operator+=(const MyString &other)
{
	if(nullptr==m_data)
	{
		init(other.m_data);
	}else{
		// std::strcat(m_data,other.m_data);				//
		std::memset(m_data+strlen(m_data),0,strlen(other.m_data)+1);
		std::memmove(m_data+strlen(m_data),other.m_data,strlen(other.m_data));
	}
	return *this;
}

char* MyString::findChar(char c)
{
	// return std::strchr(m_data,c);						//字符串操作-字符搜索函数
	return (char*)std::memchr(m_data,c,sizeof(m_data));	//字符串数组字符搜索操作
}

bool operator==(const MyString &lhs,const MyString &rhs)
{
	/*std::strcmp
	*若字典序中 lhs 先出现于 rhs 则为负值。
	*若 lhs 与 rhs 比较相等则为零。
	*若字典序中 lhs 后出现于 rhs 则为正值。
	*/
	int ret = std::strcmp(lhs.m_data,rhs.m_data);					//字符串操作-字符串比较
	// int ret = std::memcmp(lhs.m_data,rhs.m_data,sizeof(lhs.m_data));//字符串数组字符串比较
	return 0 == ret;
}

        2.5 字符串处理类调用

        上述例子,无论是采用字符串处理函数,还是采用字符数组处理函数都能实现同样的功能。采用字符数组操作更贴近内存处理的方式。

        通过上述定义自定义的MyString类,就类似std::string一样实现字符串表达及字符串处理。

//MyString.cpp
//友元函数
std::ostream& operator<<(std::ostream& output,const MyString& obj)
{
	output << std::string(obj.m_data);
	return output;
};

//test1.cpp
void mystring_test(void)
{
    MyString s1("hello");           //
    MyString s2("world");
    MyString s3(s1);                //复制构造
    std::cout << "s3.length = " << s3.length() << "\n"; //length()
    std::cout << "s3 = " << s3 << "\n";
    
    MyString s4;
    s4 = s2;                        //operator=
    std::cout << "s4.length = " << s4.length() << "\n";
    std::cout << "s4 = " << s4 << "\n";
    
    s3+=s2;                         //operator+=
    std::cout << "s3.length = " << s3.length() << "\n";
    std::cout << "s3 = " << s3 << "\n";
    
    if(s2==s4){                     //operator==
        std::cout << "s2 equal to s4\n";
    }
    std::cout << "s3.findChar('o') = "<< std::string(s3.findChar('o')) << "\n";
    MyString s5 = "hello";          //operator=(char*)
    if(s1==s5){                     //operator==
        std::cout << "s1 equal to s5\n";
    }
}
// g++ main.cpp MyString.cpp test*.cpp -o test.exe -std=c++11
//out log
s3.length = 5
s3 = hello
s4.length = 5
s4 = world
s3.length = 10
s3 = helloworld
s2 equal to s4
s3.findChar('o') = oworld
s1 equal to s5

三、字符处理

        3.1 字符操作函数集

        标准库还提供了针对单个字符(char)为主的字符处理函数,主要字符分类识别及字符数值转换两大类:

//字符分类函数,定义于头文件 <cctype>
isalnum     检查字符是否为字母或数字
isalpha     检查字符是否为字母 
islower     检查字符是否为小写 
isupper     检查字符是否为大写字符 
isdigit     检查字符是否为数字
isxdigit    检查字符是为十六进制字符 
iscntrl     检查字符是否为控制字符 
isgraph     检查字符是否为图形字符
isspace     检查字符是否为空白间隔字符
isblank     (C++11)  检查字符是否为空白字符
isprint     检查字符是否为打印字符
ispunct     检查字符是否为标点符
//字符操作函数
tolower     转换字符为小写
toupper     转换字符为大写 
 
//转换为数值格式,定义于头文件 <cstdlib>
atof              转换字节字符串为浮点值
atoi atol atoll   (C++11)转换字节字符串为整数值
strtol strtoll    (C++11)转换字节字符串为整数值 
strtoul strtoull  (C++11)转换字节字符串为无符号整数值
strtof strtod strtold 转换字节字符串为浮点值 
//定义于头文件 <cinttypes>
strtoimax strtoumax (C++11)转换字节字符串为 std::intmax_t 或 std::uintmax_t 

        3.2 字符分类函数

        字符分类函数主要是检验字符的类型的,例如,大小写、数字、图形、标点符等等,需要结合本地环境才有意义。以下是部分字符分类函数的定义描述信息:

//定义于头文件 <cctype>
/*
*检查给定字符是否为当前 C 本地环境分类为字母数字字符。默认本地环境中,下列字符为字母数字:
*◦数字( 0123456789 )
*◦大写字母( ABCDEFGHIJKLMNOPQRSTUVWXYZ )
*◦小写字母( abcdefghijklmnopqrstuvwxyz )
*若 ch 的值不能表示为 unsigned char 且不等于 EOF 则行为未定义。
*参数: ch - 要分类的字符 
*返回值 - 若字符为字母数字字符,则为非零值,否则为 0 。
*/
int isalnum( int ch );

/*
*检查给定字符是否为当前安装的 C 本地环境分类为字母字符。默认本地环境中,下列字符为字母:
*◦uppercase letters ABCDEFGHIJKLMNOPQRSTUVWXYZ
*◦lowercase letters abcdefghijklmnopqrstuvwxyz
*在异于 "C" 的本地环境中,字母字符是 std::isupper() 或 std::islower() 对它返回非零的字符,
*或本地环境认为是字母的任何其他字符。任何情况下, std::iscntrl() 、 std::isdigit() 、 
*std::ispunct() 和 std::isspace() 将对此字符返回零。
*若 ch 的值不可表示为 unsigned char 且不等于 EOF 则行为未定义。
*参数: ch - 要分类的字符 
*返回值 - 若字符为字母字符则为非零值,否则为零。
*/
int isalpha( int ch );

/*
*检查给定字符是否按照当前C本地环境分类为小写字符。
*默认 "C" 本地环境中,islower仅对小写字母( abcdefghijklmnopqrstuvwxyz )返回非零值。
*若 islower 返回非零值,则保证同一 C 本地环境中 
*std::iscntrl 、 std::isdigit 、 std::ispunct 和 std::isspace 对同一字符返回零。
*若 ch 不可表示为 unsigned char 且不等于 EOF 则行为未定义。
*参数: ch - 要分类的字符 
*返回值 - 若字符为小写字母则为非零值,否则为零
*/
int islower( int ch );

/*
*检查给定的字符是否 10 个十进制数位: 0123456789 之一。
*若 ch 的值不能表示为 unsigned char 且不等于 EOF ,则行为未定义。
*参数: ch - 要分类的字符 
*返回值 - 若字符为数字字符则为非零值,否则为零。
*/
int isdigit( int ch );

/*
*检查给定的字符是否为当前 C 本地环境分类为标点字符。
*默认 C 本地环境分类字符 !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ 为标点。
*若 ch 的值不能表示为 unsigned char 且不等于 EOF 则行为未定义。
*参数: ch - 要分类的字符 
*返回值 - 若字符为标点字符则为非零值,否则为零。
*/
int ispunct( int ch );

        下面通过上述功能函数,为自定义的MyString类提供一些字符判断支持功能:

//是否全部为字母数字
bool MyString::isall_alnum()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::isalnum(*(pc+i)))
			return false;
	}
	return true;
}
//是否全部为字母
bool MyString::isall_alpha()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::isalpha(*(pc+i)))
			return false;
	}
	return true;
}
//是否全部为小写字母
bool MyString::isall_lower()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::islower(*(pc+i)))
			return false;
	}
	return true;
}
//是否全部为十进制数字
bool MyString::isall_digit()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::isdigit(*(pc+i)))
			return false;
	}
	return true;
}
//是否有标点符号
bool MyString::ishas_punct()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0!=std::ispunct(*(pc+i)))
			return true;
	}
	return false;
}

        3.3 字符大小写转换函数

        标准库提供的字符大小写转换定义于头文件 <cctype>中,具体定义信息如下:

/*
*按照当前安装的 C 本地环境所定义的规则,转换给定字符为小写。
*默认 "C" 本地环境中,
*分别以小写字母 abcdefghijklmnopqrstuvwxyz 替换大写字母 ABCDEFGHIJKLMNOPQRSTUVWXYZ 。
*参数: ch - 要转换的字符。
*若 ch 的值不能表示为 unsigned char 且不等于 EOF ,则行为未定义 
*返回值 - ch 的小写版本,或若无列于当前 C 本地环境的小写版本,则为不修改的 ch 。
*/
int tolower( int ch );

/*
*按照当前安装的 C 本地环境所定义的字符转换规则,转换给定字符为大写。
*默认 "C" 本地环境中,
*分别以大写字母 ABCDEFGHIJKLMNOPQRSTUVWXYZ 替换下列小写字母 abcdefghijklmnopqrstuvwxyz 。
*参数: ch - 要转化的字符。
*若 ch 的值不能表示为 unsigned char 且不等于 EOF ,则行为未定义。 
*返回值 - 被转换的字符,或若当前 C 本地环境不定义大写版本则为 ch 。
*/
int toupper( int ch );

        现在再次为MyString类提供大小转换功能:

//转换为小写
void MyString::tolower()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		*(pc+i) = std::tolower(*(pc+i));
	}
}
//转换为大写
void MyString::toupper()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		*(pc+i) = std::toupper(*(pc+i));
	}
}

        3.4 字符串转数值格式

        标准库关于字符串转为数值格式(整型、浮点等)的功能函数集定义于头文件 <cstdlib>中:

//转换为数值格式,定义于头文件 <cstdlib>
atof              转换字节字符串为浮点值
atoi atol atoll   (C++11)转换字节字符串为整数值
strtol strtoll    (C++11)转换字节字符串为整数值 
strtoul strtoull  (C++11)转换字节字符串为无符号整数值
strtof strtod strtold 转换字节字符串为浮点值 
//定义于头文件 <cinttypes>
strtoimax strtoumax (C++11)转换字节字符串为 std::intmax_t 或 std::uintmax_t 

/*
*转译 str 所指向的字节字符串中的浮点值。
*函数会舍弃任何空白符(由 std::isspace() 确定),直至找到首个非空白符。
*然后它会取用尽可能多的字符,以构成合法的浮点数表示,并将它们转换成浮点值。
*参数: str - 指向要被转译的空终止字节字符串的指针 
*返回值--成功时为对应 str 内容的 double 值。
*若转换值落在返回类型范围外,则返回值未定义。若不能进行转换,则返回 0.0 
*/
double atof( const char *str );

/*
*转译 str 所指向的字节字符串中的整数值。
*舍弃任何空白符,直至找到首个非空白符,然后接收尽可能多的字符以组成合法的整数表示,
*并转换之为整数值。合法的整数值含下列部分:
*【1】(可选) 正或负号,【2】数位
*参数:str - 指向要转译的空终止字节字符串的指针 
*返回值 -- 成功时为对应 str 内容的整数值。
*若转换出的值落在对应返回类型的范围外,则返回值未定义。若不能进行转换,则返回 ​0​ 。
*/
int       atoi( const char *str );

long      atol( const char *str );

long long atoll( const char *str );// (C++11 起) 

/*其他字符串转数值格式函数类似*/

        再次为MyString类提供字符串转数值格式的功能函数:

//转为浮点数
double MyString::atof()
{
	if(nullptr==m_data) return 0.0;
	return std::atof(m_data);
}
//转为整数
int MyString::atoi()
{
	if(nullptr==m_data) return 0;
	return std::atoi(m_data);
}
//转为长整数
long MyString::atol()
{
	if(nullptr==m_data) return 0L;
	return std::atol(m_data);
}
//转发long long
long long MyString::atoll()
{
	if(nullptr==m_data) return 0LL;
	return std::atoll(m_data);
}

        3.5 字符处理业务调用

        通过上述标准库字符处理函数的支持,使得自定义类MyString也具备了针对字符进行类型识别、大小转换及数值格式转换的能力,现调用测试这些功能函数。

void mystring_char_test(void)
{
    MyString s1("335815");
    std::cout << "s1 = " << s1 << "\n";
    std::cout <<"s1.isall_alnum = " << s1.isall_alnum() << "\n";
    std::cout <<"s1.isall_alpha = " << s1.isall_alpha() << "\n";
    std::cout <<"s1.isall_digit = " << s1.isall_digit() << "\n";
    MyString s2("335aq8b15");
    std::cout << "s2 = " << s2 << "\n";
    std::cout <<"s2.isall_alnum = " << s2.isall_alnum() << "\n";
    std::cout <<"s2.isall_alpha = " << s2.isall_alpha() << "\n";
    std::cout <<"s2.isall_digit = " << s2.isall_digit() << "\n";

    MyString s3("hello");           //
    MyString s4("world!");          //
    std::cout << "s3 = " << s3 << "\n";
    std::cout << "s4 = " << s4 << "\n";
    std::cout <<"s3.isall_alpha = " << s3.isall_alpha() << "\n";
    std::cout <<"s4.isall_alpha = " << s4.isall_alpha() << "\n";
    std::cout <<"s3.isall_lower = " << s3.isall_lower() << "\n";
    std::cout <<"s4.isall_lower = " << s4.isall_lower() << "\n";
    std::cout <<"s3.ishas_punct = " << s3.ishas_punct() << "\n";
    std::cout <<"s4.ishas_punct = " << s4.ishas_punct() << "\n";
    //
    s3.toupper();
    s4.toupper();
    std::cout << "s3 = " << s3 << "\n";
    std::cout << "s4 = " << s4 << "\n";
    s3.tolower();
    s4.tolower();
    std::cout << "s3 = " << s3 << "\n";
    std::cout << "s4 = " << s4 << "\n";
    //
    MyString s5("123456");           //
    std::cout << "s5 = " << s5 << "\n";
    std::cout <<"s5.atof = " << s5.atof() << "\n";
    std::cout <<"s5.atoi = " << s5.atoi() << "\n";
    std::cout <<"s5.atol = " << s5.atol() << "\n";
    std::cout <<"s5.atoll = " << s5.atoll() << "\n";
    MyString s6("123.456");          //
    std::cout << "s6 = " << s6 << "\n";
    std::cout <<"s6.atof = " << s6.atof() << "\n";
    std::cout <<"s6.atoi = " << s6.atoi() << "\n";
    std::cout <<"s6.atol = " << s6.atol() << "\n";
    std::cout <<"s6.atoll = " << s6.atoll() << "\n";
}
//g++ main.cpp MyString.cpp test*.cpp -o test.exe -std=c++11
//out log
s1 = 335815
s1.isall_alnum = 1
s1.isall_alpha = 0
s1.isall_digit = 1
s2 = 335aq8b15
s2.isall_alnum = 1
s2.isall_alpha = 0
s2.isall_digit = 0
s3 = hello
s4 = world!
s3.isall_alpha = 1
s4.isall_alpha = 0
s3.isall_lower = 1
s4.isall_lower = 0
s3.ishas_punct = 0
s4.ishas_punct = 1
s3 = HELLO
s4 = WORLD!
s3 = hello
s4 = world!
s5 = 123456
s5.atof = 123456
s5.atoi = 123456
s5.atol = 123456
s5.atoll = 123456
s6 = 123.456
s6.atof = 123.456
s6.atoi = 123
s6.atol = 123
s6.atoll = 123

四、演示源代码补充

        编译指令:g++ main.cpp MyString.cpp test*.cpp -o test.exe -std=c++11

        main.cpp

#include "test1.h"

int main(int argc, char* argv[])
{
    char_test();
    mystring_test();
    mystring_char_test();
    return 0;
}

        MyString.h

#ifndef _MY_STRING_H_
#define _MY_STRING_H_
#include <ostream>
class MyString
{
public:
	MyString(void);							//默认构造函数
	MyString( const char *str );	        //普通构造函数
	MyString( const MyString &other );		//拷贝构造函数
	virtual ~MyString( void );				//析构函数
	MyString& operator=(const MyString &other);	//赋值函数
    MyString& operator=(const char* other);	    //赋值函数
    operator char*() const;                     //MyString转char*
	char* c_str(void) const;					//取值(取值)
    //
    MyString& operator+=(const MyString &other);//赋值运算函数
    //返回m_data的长度
    std::size_t length();
    //指向 m_data 找到的字符的指针,若未找到该字符则为空指针。
    char* findChar(char c);
    //输出屏幕
    friend std::ostream& operator<<(std::ostream& output,const MyString& obj);
    //比较函数
    friend bool operator==(const MyString &lhs,const MyString &rhs);
    //是否全部为字母数字
    bool isall_alnum();
    //是否全部为字母
    bool isall_alpha();
    //是否全部为小写字母
    bool isall_lower();
    //是否全部为十进制数字
    bool isall_digit();
    //是否有标点符号
    bool ishas_punct();
    //转换为小写
    void tolower();
    //转换为大写
    void toupper();
    //转为浮点数
    double atof();
    //转为整数
    int atoi();
    //转为长整数
    long atol();
    //转发long long
    long long atoll();
private:
    void init(const char *str);
    void copy(const char *str);
private:
	char *m_data;
};
#endif //_MY_STRING_H_

        MyString.cpp

#include "MyString.h"

#include <iostream>
#include <cstring>
//默认构造函数
MyString::MyString(void)
{
	MyString(nullptr);	//内部调用普通构造函数
}
//普通构造函数
MyString::MyString(const char *str)
{
    init(str);
}
// MyString 的析构函数
MyString::~MyString(void)
{
	delete [] m_data; // 或 delete m_data;
}

void MyString::init(const char *str)
{
	if(nullptr==str)
	{
		m_data = new char[1];	//对空字符串自动申请存放结束标志'\0'的空
		*m_data = '\0';
	}else{
		int length = strlen(str);
		m_data = new char[length+1];   // 分配内存
		// strcpy(m_data, str);		   //调用标准库的字符串拷贝函数
		std::memcpy(m_data,str,length);//调用标准库的字符数组拷贝函数
	}
};

void MyString::copy(const char *str)
{
    if(nullptr!=str)
    {
        delete [] m_data;				//释放原有的内存资源
        int length = strlen( str );
        m_data = new char[length+1];	//重新分配内存
        // strcpy( m_data, str);			//调用标准库的字符串拷贝函数
		std::memcpy(m_data,str,length); //调用标准库的字符数组拷贝函数
    }
};

//拷贝构造函数
MyString::MyString( const MyString &other ) //输入参数为const型
{
    init(other.m_data);
}

//赋值函数
MyString &MyString::operator =( const MyString &other )//输入参数为const型
{
	if(this == &other)				//检查自赋值
		return *this;	
    copy(other.m_data);	
	return *this;					//返回本对象的引用
}

MyString& MyString::operator=(const char* other)	    //赋值函数
{
    copy(other);	
    return *this;					//返回本对象的引用
};

MyString::operator char*() const
{
    return (char*)m_data;
};

char* MyString::c_str(void) const
{
	return (char*)m_data;
};

MyString& MyString::operator+=(const MyString &other)
{
	if(nullptr==m_data)
	{
		init(other.m_data);
	}else{
		// std::strcat(m_data,other.m_data);				//
		//
		std::memset(m_data+strlen(m_data),0,strlen(other.m_data)+1);
		std::memmove(m_data+strlen(m_data),other.m_data,strlen(other.m_data));
	}
	return *this;
}

std::size_t MyString::length()
{
	return std::strlen(m_data);
}

char* MyString::findChar(char c)
{
	// return std::strchr(m_data,c);						//字符串操作-字符搜索函数
	return (char*)std::memchr(m_data,c,sizeof(m_data));	//字符串数组字符搜索操作
}

std::ostream& operator<<(std::ostream& output,const MyString& obj)
{
	output << std::string(obj.m_data);
	return output;
};

bool operator==(const MyString &lhs,const MyString &rhs)
{
	/*std::strcmp
	*若字典序中 lhs 先出现于 rhs 则为负值。
	*若 lhs 与 rhs 比较相等则为零。
	*若字典序中 lhs 后出现于 rhs 则为正值。
	*/
	int ret = std::strcmp(lhs.m_data,rhs.m_data);					//字符串操作-字符串比较
	// int ret = std::memcmp(lhs.m_data,rhs.m_data,sizeof(lhs.m_data));//字符串数组字符串比较
	return 0 == ret;
}
//是否全部为字母数字
bool MyString::isall_alnum()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::isalnum(*(pc+i)))
			return false;
	}
	return true;
}
//是否全部为字母
bool MyString::isall_alpha()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::isalpha(*(pc+i)))
			return false;
	}
	return true;
}
//是否全部为小写字母
bool MyString::isall_lower()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::islower(*(pc+i)))
			return false;
	}
	return true;
}
//是否全部为十进制数字
bool MyString::isall_digit()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0==std::isdigit(*(pc+i)))
			return false;
	}
	return true;
}
//是否有标点符号
bool MyString::ishas_punct()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		if(0!=std::ispunct(*(pc+i)))
			return true;
	}
	return false;
}

//转换为小写
void MyString::tolower()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		*(pc+i) = std::tolower(*(pc+i));
	}
}
//转换为大写
void MyString::toupper()
{
	char *pc = m_data;
	int len =strlen(m_data);
	for (size_t i = 0; i < len; ++i)
	{
		*(pc+i) = std::toupper(*(pc+i));
	}
}

//转为浮点数
double MyString::atof()
{
	if(nullptr==m_data) return 0.0;
	return std::atof(m_data);
}
//转为整数
int MyString::atoi()
{
	if(nullptr==m_data) return 0;
	return std::atoi(m_data);
}
//转为长整数
long MyString::atol()
{
	if(nullptr==m_data) return 0L;
	return std::atol(m_data);
}
//转发long long
long long MyString::atoll()
{
	if(nullptr==m_data) return 0LL;
	return std::atoll(m_data);
}

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_

void char_test(void);
void mystring_test(void);
void mystring_char_test(void);
#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"
#include "MyString.h"

#include <iostream>
#include <cstring>
#include <cassert>

void f(char* pvec)
{
    assert(nullptr!=pvec);
    std::cout << std::string(pvec) << "\n";
}

void g(char cvev[])
{
    assert(nullptr!=cvev);
    std::cout << std::string(cvev) << "\n";
}

void char_test(void)
{
    char cvec[10] = "char vec";
    // char cvec[10] = {'c','h','a','r',' ','v','e','c','\0','\0'};//本质上
    if(cvec[1]==*(cvec+1))//YES
        std::cout << "cvec[1] equal to *(cvec+1)\n";
    char *pvec = cvec;
    if(pvec[1]==*(pvec+1))//YES
        std::cout << "pvec[1] equal to *(pvec+1)\n";
    // char *pvec = nullptr;
    // pvec = new char[10];
    // delete[] pvec;
    // pvec = nullptr;
    //
    char cc[] = "hello world!";
    f(cc);
    char* pc = new char[sizeof(char)*sizeof(cc)];
    memcpy(pc,cc,sizeof(char)*sizeof(cc));
    g(pc);
    delete[] pc;
    pc = nullptr;
}

void mystring_test(void)
{
    MyString s1("hello");           //
    MyString s2("world");
    MyString s3(s1);                //复制构造
    std::cout << "s3.length = " << s3.length() << "\n"; //length()
    std::cout << "s3 = " << s3 << "\n";
    
    MyString s4;
    s4 = s2;                        //operator=
    std::cout << "s4.length = " << s4.length() << "\n";
    std::cout << "s4 = " << s4 << "\n";
    
    s3+=s2;                         //operator+=
    std::cout << "s3.length = " << s3.length() << "\n";
    std::cout << "s3 = " << s3 << "\n";
    
    if(s2==s4){                     //operator==
        std::cout << "s2 equal to s4\n";
    }
    std::cout << "s3.findChar('o') = "<< std::string(s3.findChar('o')) << "\n";
    MyString s5 = "hello";          //operator=(char*)
    if(s1==s5){                     //operator==
        std::cout << "s1 equal to s5\n";
    }
}

void mystring_char_test(void)
{
    MyString s1("335815");
    std::cout << "s1 = " << s1 << "\n";
    std::cout <<"s1.isall_alnum = " << s1.isall_alnum() << "\n";
    std::cout <<"s1.isall_alpha = " << s1.isall_alpha() << "\n";
    std::cout <<"s1.isall_digit = " << s1.isall_digit() << "\n";
    MyString s2("335aq8b15");
    std::cout << "s2 = " << s2 << "\n";
    std::cout <<"s2.isall_alnum = " << s2.isall_alnum() << "\n";
    std::cout <<"s2.isall_alpha = " << s2.isall_alpha() << "\n";
    std::cout <<"s2.isall_digit = " << s2.isall_digit() << "\n";

    MyString s3("hello");           //
    MyString s4("world!");          //
    std::cout << "s3 = " << s3 << "\n";
    std::cout << "s4 = " << s4 << "\n";
    std::cout <<"s3.isall_alpha = " << s3.isall_alpha() << "\n";
    std::cout <<"s4.isall_alpha = " << s4.isall_alpha() << "\n";
    std::cout <<"s3.isall_lower = " << s3.isall_lower() << "\n";
    std::cout <<"s4.isall_lower = " << s4.isall_lower() << "\n";
    std::cout <<"s3.ishas_punct = " << s3.ishas_punct() << "\n";
    std::cout <<"s4.ishas_punct = " << s4.ishas_punct() << "\n";
    //
    s3.toupper();
    s4.toupper();
    std::cout << "s3 = " << s3 << "\n";
    std::cout << "s4 = " << s4 << "\n";
    s3.tolower();
    s4.tolower();
    std::cout << "s3 = " << s3 << "\n";
    std::cout << "s4 = " << s4 << "\n";
    //
    MyString s5("123456");           //
    std::cout << "s5 = " << s5 << "\n";
    std::cout <<"s5.atof = " << s5.atof() << "\n";
    std::cout <<"s5.atoi = " << s5.atoi() << "\n";
    std::cout <<"s5.atol = " << s5.atol() << "\n";
    std::cout <<"s5.atoll = " << s5.atoll() << "\n";
    MyString s6("123.456");          //
    std::cout << "s6 = " << s6 << "\n";
    std::cout <<"s6.atof = " << s6.atof() << "\n";
    std::cout <<"s6.atoi = " << s6.atoi() << "\n";
    std::cout <<"s6.atol = " << s6.atol() << "\n";
    std::cout <<"s6.atoll = " << s6.atoll() << "\n";
}

猜你喜欢

转载自blog.csdn.net/py8105/article/details/129776842