字符串、字符处理总结

栏目: C++ · 发布时间: 7年前

内容简介:字符串、字符处理总结

一、c中字符串的处理

char *p=”computer”;其最后一个是’\0’,

‘c’

‘o’

‘m’

‘p’

‘u’

‘t’

‘e’

‘r’

‘\0’

‘\0’

在内存中还是’c’’o’这样存的,并且操作的时候以遇到’\0’认为字符串结束了。

字符串、字符处理总结 可以以下标的形式输出字符串中的字符。

字符串、字符处理总结

不可以更改常量字符串,因为这样是放到了静态常量区。

字符串、字符处理总结

这样就更改了。”computer”存储的位置更改了。

字符串、字符处理总结

字符串、字符处理总结

为什么a输出不了,因为数组p[10]能存10个字符,布局如下

‘c’

‘o’

‘m’

‘p’

‘u’

‘t’

‘e’

‘r’

‘\0’

‘a’

字符串是以‘\0’结尾的。检测到‘\0’即认为字符串结束。故a输出不了。

字符串、字符处理总结

字符串只能存在字符数组中。

字符串、字符处理总结 a的ASCLL码为97,所以输出整形就是97.

字符串、字符处理总结

在c中没有字符串类型,所以只能采用字符指针或字符数组进行操作,对于一个字符串,若赋给字符指针,则字符串存放的位置是静态常量区,不可对其更改。若赋给字符数组,则字符串就放在系统分给该字符数组的内存中,可以对其更改。存放的结果相当于存入一个个字符,例如”computer”,放在长度为10的字符数组中,布局如下:

‘c’

‘o’

‘m’

‘p’

‘u’

‘t’

‘e’

‘r’

‘\0’

‘\0’

字符串是以‘\0’结尾的。检测到‘\0’即认为字符串结束。

sprintf_s 函数功能 :将数据格式化输出到字符串,

字符串、字符处理总结

int sprintf_s(char*buffer, size_t sizeOfBuffer, const   char *format [,argument] ... );

需要包含的头文件 :stdio.h

注意: sprintf_s()是sprintf()的安全版本,通过指定缓冲区长度来避免sprintf()存在的溢出风险。

c++中可以使用 stringstream

描述:是对 istringstreamostringstream 类的综合,支持 <<,>> 操作符,可以进行字符串到其它类型的快速转换

作用:

1 stringstream 通常是用来做数据转换的

      2 、将文件的所有数据一次性读入内存

字符串、字符处理总结

注意加上stream.clear();否则每次输出都是10. stream.clear()相当于把以前的流清空了。 由于 stringstream 构造函数会特别消耗内存,似乎不打算主动释放内存 ( 或许是为了提高效率 ) ,但如果你要在程序中用同一个流,反复读写大量的数据,将会造成大量的内存消耗,因些这时候,需要适时地清除一下缓冲 ( stream.str("") )

字符串、字符处理总结

二、数组或字符串的长度

1、sizeof()---求所占的字节数

#include <string>
#include <iostream> 
using namespace std;
int main()
{
	//(1)、对于整型字符型数组
	int A[] = { 1, 4, 5, 2, 8, 6, 0 };
	//求整型数组A所占的字节数
	cout << "sizeof(A) = " << sizeof(A) << endl; //整型数组A所占的总空间的字节数
	cout<<"sizeof(A) / sizeof(int)="<<sizeof(A) / sizeof(int)<<endl;//此时i表示数字数组的元素个数
	char B[] = { 'a', 'e', 's', 'r', 'q' };
	//求字符数组的所占的字节数
	cout << "sizeof(B)=" << sizeof(B) << endl;//结果为5*sizeof(char)=5
	//求字符数组的元素个数
	cout << "sizeof(B) / sizeof(char)=" << sizeof(B) / sizeof(char) << endl;
	char C[] = "abcde";
	cout << "i = sizeof(C)=" << sizeof(C) << endl;//字符数组C所占的字节空间,为6,最后一个为’\0’
	//(2)、对于整型或字符型指针
	int *p=NULL;
	int D[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	p = D;
	cout << "sizeof(*p)="<<sizeof(*p) << endl;//p所指的整型数组A的第一个元素A[0]的所占字节数
	cout << "sizeof(p)=" << sizeof(p) << endl;
	cout << *p << endl;  //输出A[0]即1
	cout << p << endl;   //输出的结果为整型指针所指向的地址 0x……

	char *p1;
	char E[] = { 'a', 's', 'e', 'r' };
	p1 = E;  //字符指针赋值,或char *p=B;这样为字符指针初始化赋值
	cout << "sizeof(p1)" << sizeof(p1) << endl;//p为字符指针,指针的大小为定值,为4
	cout << "sizeof(*p1)" << sizeof(*p1)<< endl;//这是指B[0]所占空间的大小 

	system("pause");
	return 0;
}

字符串、字符处理总结

2、strlen()---字符数组或字符串所占的字节数

strlen所作的仅仅是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描, 直到碰到第一个字符串结束符'\0'为止(注意存储在数组中时易出错) ,然后返回计数器值。 --就是指实际字符串或字符数组的实际长度(不是所占空间的字节数)。

#include <string>
#include <iostream> 
using namespace std;
class Syf
{
	int i;
	int j;
	char k;
};
Syf x;
class A1
{
};
class A2
{
};
class B1 :public A1
{
};
class C1 :virtual public B1
{
};
class D1 :public A1, public A2
{
};
int main()
{
	char A[6] = { 'a', 'b', '\0', 'd', 'e', 'r' };
	cout << "strlen(A)="<<strlen(A) << endl;//因为到’\0’结束,故实际A[]只有2个元素 
	char *str = "abcde";
	cout <<"strlen(str)="<< strlen(str) << endl;
	//char A[6]={"abcdef"}; //error C2117: 'abcdef' : array bounds overflow

	//针对字符指针
	char C[] = { "abcdef" };
	char *p1 = C;
	cout << "strlen(p1)="<<strlen(p1) << endl;
	char D[] = { 'a', 'c', 'q', 'f', 'w' };
    //这样,由于没指定D的内存分配大小,用strlen求其长度会造成错误。 
	//如果为char D[5]={'a','c','q','f','w'};这样再用strlen求其长度也会造成错误,当D[]之中的数 
	//大于5才不会造成错误。 似乎因为strlen是判断\0才结束的。
	cout << "strlen(D)=" << strlen(D) << endl;

	//针对其他结构
	cout << "sizeof(Syf)="<<sizeof(Syf) << endl; //结果 12 == = 》内存补齐
	cout <<"sizeof(x)="<< sizeof(x) << endl;// 结果 12 同上
	//解释一下,在class X中,成员中最大的所占的空间为int类型所占的空间4个字节,故内存补齐。

	//有关空类
	//一个空类占多少空间?多重继承呢?	
	cout << "sizeof(A1): " << sizeof(A1) << endl;
	cout << "sizeof(B1): " << sizeof(B1) << endl;
	cout << "sizeof(C1): " << sizeof(C1) << endl;
	cout << "sizeof(D1): " << sizeof(D1) << endl;

	system("pause");
	return 0;
}

字符串、字符处理总结

表明空类所占空间为1个字节,单一继承的空类空间也为1,多重继承的空类空间还是1,但虚继承涉及虚表(虚指针),所以sizeof(C)为 4。

3、sizeof()与strlen()区别

sizeof()返回的是变量声明后所占的内存数,不是实际长度,此外sizeof不是函数,仅仅是一个操作符,strlen是函数。 

4、c++中的字符串string的长度

字符串、字符处理总结

为了兼容等,这两个函数一样。 length是因为沿用 C语言 的习惯而保留下来的,string类最初只有length,引入STL之后,为了兼容又加入了size,它是作为STL容器的属性存在的,便于符合STL的接口规则,以便用于STL的算法。 
string类的size()/length()方法返回的是字节数,不管是否有汉字。

三、C++中string类介绍

之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必担心内存是否足够、字符串长度等等,而且作为一个类出现,他集成的操作函数足以完成我们大多数情况下(甚至是100%)的需要。我们可以用 = 进行赋值操作,== 进行比较,+ 做串联。我们尽可以把它看成是C++的基本数据类型。

首先,为了在我们的程序中使用string类型,我们必须包含头文件 <string>。

如下:
#include <string> //注意这里不是string.h,string.h是C字符串头文件
using namespace std;

1.声明一个C++字符串

声明一个字符串变量很简单: string Str;

这样我们就声明了一个字符串变量,但既然是一个类,就有构造函数和析构函数。上面的声明没有传入参数,所以就直接使用了string的默认的构造函数,这个函数所作的就是把Str初始化为一个空字符串。String类的构造函数和析构函数如下:

a)      string s;    //生成一个空字符串s

b)      string s(str) //拷贝构造函数 生成str的复制品

c)      string s(str,stridx) //将字符串str内“始于位置stridx”的部分当作字符串的初值

d)      string s(str,stridx,strlen) //将字符串str内“始于stridx且长度顶多strlen”的部分作为字符串的初值

e)      string s(cstr) //将C字符串作为s的初值

f)      string s(chars,chars_len) //将C字符串前chars_len个字符作为字符串s的初值。

g)      string s(num,c) //生成一个字符串,包含num个c字符

h)      string s(beg,end) //以区间beg;end(不包含end)内的字符作为字符串s的初值

i)      s.~string() //销毁所有字符,释放内存

2.字符串操作函数

这里是C++字符串的重点,我先把各种操作函数罗列出来,需要的时候再看他的详细解释。( 使用下面的函数的时候要加上对象,指明谁在用。例s1.size()

a) =,assign()     //赋以新值

b) swap()     //交换两个字符串的内容

c) +=,append(),push_back() //在尾部添加字符

d) insert() //插入字符串

e) erase() //删除字符

f) clear() //删除全部字符

g) replace() //替换字符

h) + //串联字符串

i) ==,!=,<,<=,>,>=,compare()    //比较字符串

j) size(),length()    //返回字符数量

k) max_size() //返回字符的可能最大个数

l) empty()    //判断字符串是否为空

m) capacity() //返回重新分配之前的字符容量

n) reserve() //保留一定量内存以容纳一定数量的字符

o) [ ], at() //存取单一字符

p) >>,getline() //从stream读取某值

q) <<    //将谋值写入stream

r) copy() //将某值赋值为一个C_string

s) c_str() //将内容以C_string返回

t) data() //将内容以字符数组形式返回

u) substr() //返回某个子字符串

v)查找函数,find,注意:

从pos位置开始寻找,默认从头开始

结果:找到 -- 返回 第一个字符的索引

           没找到--返回   string::npos

w)begin() end() //提供类似STL的迭代器支持

x) rbegin() rend() //逆向迭代器

y) get_allocator() //返回配置器

3、下面针对常用的进行详细介绍:

3.1、C++字符串和C字符串的转换

C++提供的由C++字符串得到对应的C_string的方法是使用data()、c_str()和copy(),其中,data()以字符数组的形式返回字符串内容,但并不添加'/0'。c_str()返回一个以‘/0'结尾的字符数组,而copy()则把字符串的内容复制或写入既有的c_string或 字符数组内。C++字符串并不以'/0'结尾。

这段摘自:http://blog.csdn.net/wangshubo1989/article/details/50281869

3.1.1 string转const char* 

标准库的string类提供了3个成员函数来从一个string得到c类型的字符数组:c_str()、data()、copy(p,n)。
1. c_str():生成一个const char*指针,指向以空字符终止的数组 注:
这个数组的数据是临时的,当有一个改变这些数据的成员函数被调用后,其中的数据就会失效。因此要么现用先转换,要么把它的数据复制到用户自己可以管理的内存中 。注意。看下例:

const char* c;  
std::string s = "1234";  
c = s.c_str();  
std::cout << c << std::endl; //输出:1234  
s = "abcd";  
std::cout << c << std::endl; //输出:abcd  

上面如果继续用c指针的话,导致的错误将是不可想象的。就如:1234变为abcd

其实上面的c = s.c_str(); 不是一个好习惯。既然c指针指向的内容容易失效,我们就应该按照上面的方法,那怎么把数据复制出来呢?这就要用到strcpy, strcpy_s等函数(推荐)。

char* c = new char;  
std::string s = "1234";  
// c = s.c_str();  
strcpy_s(c, s.size() + 1, s.c_str());  
std::cout << c << std::endl; //输出:1234  
s = "abcd";  
std::cout << c << std::endl; //输出:1234  

注意:不能再像上面一样①所示了,const还怎么向里面写入值啊;也不能②所示,使用了未初始化的局部变量“c”,运行会出错的 。

② c_str()返回一个客户程序可读不可改的指向字符数组的指针,不需要手动释放或删除这个指针。指针的生命周期与string一致。

2. data():与c_str()类似,但是返回的数组不以空字符终止。(不一定)

3.1.2 string转char* 

可以使用strcpy:

string s = "what fucking day";
char* c;
const int len = s.length();
c =new char[len+1];
strcpy(c,s.c_str());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

也可以使用copy:

int main()
{
  std::string foo("quuuux");
  char bar[7];
  foo.copy(bar, sizeof bar);
  bar[6] = '\0';
  std::cout << bar << '\n';
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3.1.3 const char*转string 

3.1.4 char*转string 

简单了:

char* c ="abc";
string s(c);
  • 1
  • 2
  • 1
  • 2

================================================================ 

string和char*指针一样大的实现很常见,也很容易找到string是char*7倍大小的string实现。为什么会有差别?为了理解这一点,我们必须知道string可能存什么数据和它可能决定保存在哪里。

实际上每个string实现都容纳了下面的信息:

  • 字符串的 大小 ,也就是它包含的字符的数目.一个size_t.
  • 可以容纳字符串字符的内存 容量 。一个size_t. 
  • 这个字符串的 ,也就是,构成这个字符串的字符。一个char*

另外,一个string可能容纳

  • 它的 配置器 的拷贝。

依赖引用计数的string实现也包含了

  • 这个值的 引用计数

不同的string实现以不同的方式把这些信息放在一起。

3.2  data和c_str的区别

上面提到了data和c_str的区别,那么究竟区别在哪呢? 
二者原型:

const value_type *c_str( ) const; 
const value_type *data( ) const;

data只是返回原始数据序列,没有保证会用traits::eos(),或者说’\0’来作字符串结束. 当然,可能多数实现都这样做了。 
c_str是标准的做法,返回的char* 一定指向一个合法的用’\0’终止的C兼容的字符串。 
所以,如果需要C兼容的字符串,c_str是标准的做法,data并不保证所有STL的实现的一致性。

你或许会问,c_str()的功能包含data(),那还需要data()函数干什么?看看源码:

const charT* c_str () const
{

   if  (length () == 0)

        return "";

   terminate ();

   return data ();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

原来c_str()的流程是:先调用terminate(),然后在返回data()。因此如果你对效率要求比较高,而且你的处理又不一定需要以\0的方式结束,你最好选择data()。但是对于一般的C函数中,需要以const char*为输入参数,你就要使用c_str()函数。 
对于c_str() data()函数,返回的数组都是由string本身拥有,千万不可修改其内容。其原因是许多string实现的时候采用了引用机制,也就是说,有可能几个string使用同一个字符存储空间。而且你不能使用sizeof(string)来查看其大小。详细的解释和实现查看Effective STL的条款15:小心string实现的多样性。 
另外在你的程序中,只在需要时才使用c_str()或者data()得到字符串,每调用一次,下次再使用就会失效。

3.3 c++的string最后带'\0'吗

针对第一个作者有句话, segmentfault 上的一个问题及得分较多的一个解答 ,以供参考

C++ 中的 std::string C-style string 是两种不同的字符串,前者是标准库中定义的一个类,后者是字符数组的别名。

  • C-style string:通常都以 \0 作为结尾。
  • std::string :标准中未规定需要 \0 作为字符串结尾。编译器在实现时既可以在结尾加 \0 ,也可以不加。但是,当通过 c_str()data() (二者在 C++11 及以后是等价的)来把 std::string 转换为 const char * 时,会发现最后一个字符是 \0 (原因见文末附录)。

C-style string 中一般不能包含 \0 字符(因为这个字符被当作表示字符串结束的特殊字符处理了),如果包含这个字符,那么其后的字符将会被忽略。即:

char s[] = "ab\0c" ;

cout << s << endl;  // 输出 ab

std::string 没有这个限制。即:

std::string s{ 'a' , 'b' , '\0' , 'c' };

//std ::string s = "ab\0c" // 这里由于是从 C -style string 构造 std::string ,所以仍然会忽略 \ 0 之后的字符

cout<< s << endl;  // 输出 ab c

附录

通过 c_str() data() (二者在 C++11 及以后是等价的)来把 std::string 转换为 const char * 时,会发现最后一个字符是 \0 。想理解这个问题需要一点背景知识:

一, std::string     std::basic_string<CharT, Traits, Allocator> 这个模板的特例 std::basic_string<char> 。即模板:

template <

classCharT,

classTraits =std::char_traits<CharT>,

classAllocator =std::allocator<CharT>

> class basic_string;

中的参数 CharT char 时的特例。

二, s.size() 会返回 std::string 中字符的个数,即:

string s{ 'a' , 'b' , '\0' , 'c' };

cout << s.size() << endl;  // 输出 4

s += '\0' ;

s += 'd' ;

cout << s.size() << endl;  // 输出 6

三,使用 [] 运算符(即 std::basic_string::referencestd::basic_string::operator[](size_type pos); )获取 std::string 中的字符时,其字符的范围 pos 是从 0 size() 。其中 0 size()-1 是所存储的字符,而对于 pos == size() 有如下规定:

If  pos == size() , a reference tothe character with value  CharT()  (the null character) is returned. Forthe first (non-const) version, the behavior is undefined if this character ismodified.

意思是当访问 s[s.size()] 时,会返回 CharT() 的返回值的引用,而 CharT 对于 std::string char ,且 char() 返回的是 \000 ,所以 s[s.size()] 会返回 \0

四,通过 c_str() data() const CharT* c_str()const; )返回的指针访问字符串时,其效果为:

Returns: Apointer  p  such that  p + i == &operator[](i)  for each  i  in [ 0 , size() ].

p[s.size()] 等价于 p + s.size() 等价于 &s.operator[](s.size()) 等价于 s[s.size()] ,所以会发现返回值是 \0

下面是我在Visual2013上的一些操作测试:

#include <string>
#include <iostream>  
using namespace std;
int main()
{
	std::cout << "string 初始化及一些操作测试" << endl;
	string str = "Then";
	std::cout << "测试这样能否输出字符,可以。str[2]:" << str[2] << endl;
//	cout << "str[5]:" << str[5] << endl;//出现异常
	str[2] = 'a';
	std::cout << "测试这样能否修改字符串,可以。str[2]:" << str[2] << endl;
	str.insert(1, "h");
	string str1 = str;
	std::cout << "测试插入与直接复制string。str1:" << str1 << endl;

	std::cout << "string 中字符\0与字符串\0的测试" << endl;
	char *p = "ab\0c";
	string s = "ab\0c";
	string s1{ 'a', 'b', '\0', 'c' };
	string s2 = "myself";
	std::cout << "p:" <<p<< endl;
	std::cout << "s:" << s << endl;
	std::cout << "s1:" << s1 << endl;
	s2 += "\0";
	std::cout << "s2:" << s2 << endl;
	s2 += "e";
	std::cout << "测试字符串斜线0能否输出,答案不可以。s2:" << s2 << endl;
	s2 += 'e';
	std::cout << "s2:" << s2 << endl;
	s2 += '\0';
	std::cout << "s2:" << s2 << endl;
	s2 += 'e';
	std::cout << "测试字符斜线0能否输出,答案可以。s2:" << s2 << endl;

	std::cout << "string 数组测试"<< endl;
	string name[3] = {
		"zhang", "wang", "li"	
	};
	std::cout << "name[2]:" << name[2] << endl;
	std::cout << "name:" << sizeof(name) << endl;

	//c_str()与data()的测试
	std::cout << "c_str()与data()的测试"<<endl;
	const char *haha = str.c_str();
	std::cout << "*haha:" << *haha << endl;
	std::cout << "*haha:" << haha << endl;
	const char *haha1 = str.data();
	std::cout << "*haha1:" << *haha1 << endl;
	std::cout << "*haha1:" << haha1 << endl;
	system("pause");
	return 0;
}

字符串、字符处理总结

我们可以使用下标操作符[]和函数at()对元素包含的字符进行访问 。但是应该注意的是操作符[]并不检查索引是否有效(有效索引0~str.length()),如果索引失效,会引起未定义的行为。而at()会检查, 如果使用 at()的时候索引无效,会抛出out_of_range异常

有一个例外不得不说,const string a;的操作符[]对索引值是a.length()仍然有效,其返回值是'/0'。其他的各种情况,a.length()索引都是无效的。

3.4 更改内容

首先讲赋值,第一个赋值方法当然是使用操作符=,新值可以是string(如:s=ns) 、c_string(如:s=”gaint”)甚至单一字符(如:s='j')还可以使用成员函数assign() ,这个成员函数可以使你更灵活的对字符串赋值。

把字符串清空的方法有三个:s=””;s.clear();s.erase();

string提供了很多函数用于插入(insert)、删除(erase)、替换(replace)、增加字符。

也许你需要在string中间的某个位置插入字符串,这时候你可以用insert()函数,这个函数需要你指定一个安插位置的索引,被插入的字符串将放在这个索引的后面。
s.insert(0,”my name”);
s.insert(1,str);
这种形式的insert()函数不支持传入单个字符,这时的单个字符必须写成字符串形式(让人恶心)。既然你觉得恶心,那就不得不继续读下面一段话:为了插 入单个字符,insert()函数提供了两个对插入单个字符操作的重载函数:insert(size_type index,size_type num,chart c)和insert(iterator pos,size_type num,chart c)。 其中size_type是无符号整数,iterator是char*,所以,你这么调用insert函数是不行的:insert(0,1, 'j');这时候第一个参数将转换成哪一个呢?所以你必须这么写:insert((string::size_type)0,1,'j')!第二种形式指 出了使用迭代器安插字符的形式,在后面会提及。顺便提一下,string有很多操作是使用STL的迭代器的,他也尽量做得和STL靠近。

删除函数erase()的形式也有好几种(真烦!),替换函数replace()也有好几个。

3.5 大小和容量函数

个C++字符串存在三种大小:a)现有的字符数,函数是size()和length(),他们等效。empty()用来检查字符串是否为空。 b)max_size() 这个大小是指当前C++字符串最多能包含的字符数,很可能和机器本身的限制或者字符串所在位置连续内存的大小有关系。我们一般情况下不用关心他,应该大小足够我们用的。但是不够用的话,会抛出length_error异常c)capacity()重新分配内存之前 string所能包含的最大字符数。这里另一个需要指出的是 reserve()函数,这个函数为string重新分配内存。重新分配的大小由其参数决定, 默认参数为0,这时候会对string进行非强制性缩减。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++数据结构与算法

C++数据结构与算法

[美]乔兹德克(Adam Drozdek) / 徐丹、吴伟敏 / 清华大学出版社 / 2014-10-1 / 63.00元

本书全面系统地介绍了数据结构,并以C++语言实现相关的算法。书中主要强调了数据结构和算法之间的联系,使用面向对象的方法介绍数据结构,其内容包括算法的复杂度分析、链表、栈、队列、递归、二叉树、图、排序和散列。书中还清晰地阐述了同类教材中较少提到的内存管理、数据压缩和字符串匹配等主题。书中包含大量的示例分析和图形,便于读者进一步理解和巩固所学的知识。一起来看看 《C++数据结构与算法》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具